テクニカル Q&A

SND19 - 音声を映像に同期させる方法 (1999 年 10 月 5 日)


Q: Mac OS 9.0 と Sound Manager 4.0 では、自作のサウンドコールバックが定期的に呼び出されなくなりました。そうなると、音声を映像に同期させるのがたいへん困難です。サウンドコールバックが定期的に呼び出されるようにするにはどうしたらいいでしょうか。

A: Sound Manager 4.0 の大きな変更点は、DMA エンジン、ひいてはハードウェアに音声を渡すとすぐにコールバックが呼び出されること、そして DMA バッファのサイズが増えたことです。つまりコールバックが以前より少し早く呼び出されることになります。ハードウェアバッファ (この場合は DMA バッファ) より小さいバッファを使っていると問題は深刻です。

ハードウェアバッファより少ない音声データを持つバッファは、ハードウェアバッファに完全にコピーされてしまいます。この時点で音声出力は「完了」とみなされ、サウンドチャネルキューの次のコマンドが呼び出されます。通常、キューにある次のコマンドは、callBackCmd です。コールバックが呼ばれたときに音声出力は完了したものと期待していると、おそらくまだ完了しておらず、問題にまきこまれることになります。

解決方法は簡単です。再生するバッファのサイズを増やしてください。最低でもハードウェアバッファのサイズまでバッファのサイズを増やします。ほとんどの場合、ハードウェアバッファの 2 倍にするのがいいでしょう。

現在、Sound Manager 4.0 では、VM がオンでないとバッファのサイズは 512 サンプル、VM がオンだと 4096 サンプルです。

しかし、将来的に変更されう可能性がありますから、これらの値を前提にしてはいけません。これらの値は、次のコードを使って、ハードウェアに問い合わせてください。

/*
    出力バッファのサイズをバイト数で返す
*/
static long GetSoundOutputBufferSize (Component outputDevice, 
    short sampleSize, short numChannels,
        UnsignedFixed sampleRate) {
    SoundComponentData  outputFormat;
    OSErr               err;
    SndChannelPtr       chan            = nil;
    SndCommand          cmd;
    ExtSoundHeader      sndHeader;
    long                bufSize         = 0;
            
    err = SndNewChannel (&chan, 0, 0, nil);
            
    sndHeader.samplePtr = nil;
    sndHeader.numChannels = numChannels;
    sndHeader.sampleRate = sampleRate;
    sndHeader.loopStart = 0;
    sndHeader.loopEnd = 0;
    sndHeader.encode = extSH;
    sndHeader.baseFrequency = kMiddleC;
    sndHeader.numFrames = 0;
    sndHeader.markerChunk = nil;
    sndHeader.instrumentChunks = nil;
    sndHeader.AESRecording = nil;
    sndHeader.sampleSize = sampleSize;
    sndHeader.futureUse1 = 0;
    sndHeader.futureUse2 = 0;
    sndHeader.futureUse3 = 0;
    sndHeader.futureUse4 = 0;
    sndHeader.sampleArea[0] = 0;
            
    // Sound Manager はこの値を無視するので実際には不要
    UnsignedFixedTox80 (sampleRate, &sndHeader.AIFFSampleRate);
            
    // 問合せのためサウンドチャネルをセットアップ
    cmd.cmd = soundCmd;
    cmd.param2 = (long)&sndHeader;
    err = SndDoCommand (chan, &cmd, true);
            
    if (err == noErr) {
        err = GetSoundOutputInfo (outputDevice, siHardwareFormat,
            &outputFormat);
    }
            
#if DEBUG
    if (err != noErr) {
        printf ("Got error #%d trying to do GetSoundOutputInfo with
        siHardwareFormat¥n¥n", err);
    } else {
        bufSize = outputFormat.sampleCount * (sampleSize / 8) * numChannels;
        printf ("Sound output buffer is %d samples, ", outputFormat.sampleCount);
        printf ("which is %d bytes.¥n¥n", bufSize);
    }
#endif
            
    return (bufSize);
}

         
         

GetSoundOutputInfo 呼び出しが失敗した場合の次善の策として、音声出力バッファのサイズは、音声入力バッファのサイズと同じと仮定することができます。入力バッファのサイズは次の呼び出しで取得できます。

/*
    入力バッファのサイズをバイト数で返す
*/
static long GetSoundInputBufferSize (UInt32 siRefNum) {
    OSErr               err;
    long                inputBufferSize;
    
    err = SPBGetDeviceInfo (siRefNum, siDeviceBufferInfo, &inputBufferSize);
    
#if DEBUG
    if (err != noErr) {
        printf ("¥nGet device buffer info error, err: %d¥n", err);
    } else {
        printf ("¥nSound input buffer is %d bytes¥n", inputBufferSize);
    }
#endif
    
    return (inputBufferSize);
}

         
         


-- Mark Cookson
Worldwide Developer Technical Support

テクニカル Q&A | 目次

To contact us, please use the Contact Us page.